using System.Net;
using System.Net.Sockets;

namespace Dong
{
    public class Client
    {
        public static Socket Connect(string ip, int port)
        {
            Socket socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Connect(ip, port);
            int checkBufferLength = 3;
            var buffer = new byte[checkBufferLength];
            ReceiveToFullBytes(socket, buffer, 0, checkBufferLength);
            for (int i = 0; i < checkBufferLength; ++i)
            {
                buffer[i] = (byte)(byte.MaxValue - buffer[i]);
            }
            socket.Send(buffer);
            var localEndPoint = socket.LocalEndPoint as IPEndPoint;
            socket.Send(BitConverter.GetBytes(localEndPoint!.Address.Address));
            socket.Send(BitConverter.GetBytes(localEndPoint!.Port));
            socket.Send(BitConverter.GetBytes(0));
            buffer = new byte[12];
            ReceiveToFullBytes(socket, buffer, 0, 4);
            int clientCount = BitConverter.ToInt32(buffer, 0);
            for (int i = 0; i < clientCount; ++i)
            {
                ReceiveToFullBytes(socket, buffer, 0, buffer.Length);
                var publicPoint = new IPEndPoint(IPAddress.Parse($"{buffer[0]}.{buffer[1]}.{buffer[2]}.{buffer[3]}"), BitConverter.ToInt32(buffer, 8));
                System.Console.WriteLine("Public endpoint: {0}", publicPoint);
                ReceiveToFullBytes(socket, buffer, 0, buffer.Length);
                var privatePoint = new IPEndPoint(IPAddress.Parse($"{buffer[0]}.{buffer[1]}.{buffer[2]}.{buffer[3]}"), BitConverter.ToInt32(buffer, 8));
                System.Console.WriteLine("Private endPoint: {0}\n", privatePoint);
            }
            return socket;
        }

        public static void WaitForConnection(Socket socket)
        {
            var buffer = new byte[12];
            ReceiveToFullBytes(socket, buffer, 0, buffer.Length);
            var publicPoint = new IPEndPoint(IPAddress.Parse($"{buffer[0]}.{buffer[1]}.{buffer[2]}.{buffer[3]}"), BitConverter.ToInt32(buffer, 8));
            ReceiveToFullBytes(socket, buffer, 0, buffer.Length);
            var privatePoint = new IPEndPoint(IPAddress.Parse($"{buffer[0]}.{buffer[1]}.{buffer[2]}.{buffer[3]}"), BitConverter.ToInt32(buffer, 8));

            //Connect to remote client
            Socket s = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
            s.Bind(socket.LocalEndPoint!);
            s.Listen(1);
            Task.Run(() =>
            {
                Socket s1 = s.Accept();
                System.Console.WriteLine("Connected. Remote ip is {0}", s1.RemoteEndPoint);
            });
            Task.Run(() =>
            {
                Socket s1 = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ;
                s1.Connect(publicPoint);
                System.Console.WriteLine("Connect to {0}.", publicPoint);
            });
            Task.Run(() =>
            {
                Socket s1 = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ;
                s1.Connect(privatePoint);
                System.Console.WriteLine("Connect to {0}.", privatePoint);
            });
        }

        public static void ConnectTo(Socket socket, string ip, int port)
        {
            socket.Send(BitConverter.GetBytes(1));
            var endPoint = new IPEndPoint(IPAddress.Parse(ip), port);
            socket.Send(BitConverter.GetBytes(endPoint.Address.Address));
            socket.Send(BitConverter.GetBytes(port));
            var buffer = new byte[12];
            ReceiveToFullBytes(socket, buffer, 0, buffer.Length);
            var publicPoint = new IPEndPoint(IPAddress.Parse($"{buffer[0]}.{buffer[1]}.{buffer[2]}.{buffer[3]}"), BitConverter.ToInt32(buffer, 8));
            ReceiveToFullBytes(socket, buffer, 0, buffer.Length);
            var privatePoint = new IPEndPoint(IPAddress.Parse($"{buffer[0]}.{buffer[1]}.{buffer[2]}.{buffer[3]}"), BitConverter.ToInt32(buffer, 8));

            //Connect to remote client
            Socket s = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
            s.Bind(socket.LocalEndPoint!);
            s.Listen(1);
            Task.Run(() =>
            {
                Socket s1 = s.Accept();
                System.Console.WriteLine("Connected. Remote ip is {0}", s1.RemoteEndPoint);
            });
            Task.Run(() =>
            {
                Socket s1 = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ;
                s1.Connect(publicPoint);
                System.Console.WriteLine("Connect to {0}.", publicPoint);
            });
            Task.Run(() =>
            {
                Socket s1 = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ;
                s1.Connect(privatePoint);
                System.Console.WriteLine("Connect to {0}.", privatePoint);
            });
        }

        private static void ReceiveToFullBytes(Socket socket, byte[] buffer, int offset, int length)
        {
            int receivedCount = 0;
            while (receivedCount < length)
            {
                receivedCount += socket.Receive(buffer, offset + receivedCount, length - receivedCount, SocketFlags.None);
            }
        }
    }
}